﻿using Cysharp.Threading.Tasks;
using Internal.KuiHuaBaoDian.Services.Lockstep;
using KuiHuaBaoDian.Services.Lockstep;
using System;
using System.Collections.Generic;
using UnityEngine.Assertions;

namespace KuiHuaBaoDian.Services.Lockstep {

    /// <summary>
    /// 帧同步计时器服务
    /// </summary>
    public interface ILockstepTimerService {

        UniTask Start(F64 time,
                      uint count = 1,
                      F64 delay = default,
                      ILockstepTimer.DataDelegate onTimer = null,
                      ILockstepTimer.DataDelegate onComplete = null,
                      ILockstepTimer.DataDelegate onStart = null,
                      ILockstepTimer.DataDelegate onKill = null,
                      ILockstepTimer.DataDelegate onUpdate = null,
                      ILockstepTimer.DataDelegate onTimerStart = null,
                      LockstepTimerCancellationToken cancellationToken = null,
                      bool isForceAttach = false);

        LockstepTimerHandle Add(F64 time,
                                uint count = 1,
                                F64 delay = default,
                                bool isAutoKill = true,
                                ILockstepTimer.DataDelegate onTimer = null,
                                ILockstepTimer.DataDelegate onComplete = null,
                                ILockstepTimer.DataDelegate onStart = null,
                                ILockstepTimer.DataDelegate onKill = null,
                                ILockstepTimer.DataDelegate onUpdate = null,
                                ILockstepTimer.DataDelegate onTimerStart = null,
                                bool isForceAttach = false);

        void Remove(LockstepTimerHandle handle);

        bool IsPausing(LockstepTimerHandle handle);

        void Pause(LockstepTimerHandle handle);

        void Resume(LockstepTimerHandle handle);
    }

    /// <summary>
    /// 帧同步计时器服务
    /// </summary>
    public static class LockstepTimerService {

        /// <summary>
        /// 单例
        /// </summary>
        public static ILockstepTimerService Instance => m_Instance ??= KHBD.TypeMap.Instantiate<ILockstepTimerService>();
        private static ILockstepTimerService m_Instance;
    }

    [Implement(typeof(ILockstepTimerService))]
    internal sealed class KHBD_LockstepTimerService : ILockstepTimerService, IService, ILockstepTimerServiceInternal {

        private readonly IDictionary<LockstepTimerHandle, ILockstepTimer> m_Dictionary = new Dictionary<LockstepTimerHandle, ILockstepTimer>();
        private readonly Stack<ILockstepTimer> m_Pool = new();

        public KHBD_LockstepTimerService() {
            LockstepService.Instance.Stopped += OnLockstepServiceStopped;
        }

        private void OnLockstepServiceStopped() {
            foreach (var kv in m_Dictionary) {
                var timer = kv.Value;
                var reusable = (ILockstepTimerServiceInternal.IReusable)timer;
                reusable.Recycle();
                m_Pool.Push(timer);
            }
            m_Dictionary.Clear();
        }

        void IService.OnApplicationQuit() {
            foreach (var kv in m_Dictionary) {
                var reusable = (ILockstepTimerServiceInternal.IReusable)kv.Value;
                reusable.Recycle();
            }
            m_Dictionary.Clear();
            m_Pool.Clear();
        }

        public LockstepTimerHandle Add(F64 time,
                                       uint count = 1,
                                       F64 delay = default,
                                       bool isAutoKill = true,
                                       ILockstepTimer.DataDelegate onTimer = null,
                                       ILockstepTimer.DataDelegate onComplete = null,
                                       ILockstepTimer.DataDelegate onStart = null,
                                       ILockstepTimer.DataDelegate onKill = null,
                                       ILockstepTimer.DataDelegate onUpdate = null,
                                       ILockstepTimer.DataDelegate onTimerStart = null,
                                       bool isForceAttach = false) {
            var timer = AddTimer(time, count, delay, isAutoKill, onTimer, onComplete, onStart, onKill, onUpdate, onTimerStart, isForceAttach);
            return timer.Handle;
        }

        private ILockstepTimer AddTimer(F64 time,
                                        uint count = 1,
                                        F64 delay = default,
                                        bool isAutoKill = true,
                                        ILockstepTimer.DataDelegate onTimer = null,
                                        ILockstepTimer.DataDelegate onComplete = null,
                                        ILockstepTimer.DataDelegate onStart = null,
                                        ILockstepTimer.DataDelegate onKill = null,
                                        ILockstepTimer.DataDelegate onUpdate = null,
                                        ILockstepTimer.DataDelegate onTimerStart = null,
                                        bool isForceAttach = false) {
            Assert.IsTrue(LockstepService.Instance.IsEnabled);

            time = time >= 0 ? time : F64.Zero;
            if (!m_Pool.TryPop(out var timer)) {
                timer = KHBD.TypeMap.Instantiate<ILockstepTimer>();
            }
            timer.Start += _ => onStart?.Invoke(timer);
            timer.Kill += _ => {
                onKill?.Invoke(timer);
                var reusable = (ILockstepTimerServiceInternal.IReusable)timer;
                reusable.Recycle();
                m_Pool.Push(timer);
            };
            timer.Timer += _ => onTimer?.Invoke(timer);
            timer.Complete += _ => onComplete?.Invoke(timer);
            timer.Update += _ => onUpdate?.Invoke(timer);
            timer.TimerStart += _ => onTimerStart?.Invoke(timer);

            var handle = isForceAttach ? (LockstepTimerHandle)(LockstepService.Instance as ILockstepServiceInternal).AttachEntity(timer) : (LockstepTimerHandle)LockstepService.Instance.AttachEntity(timer);
            m_Dictionary.Add(handle, timer);

            var @internal = (ILockstepTimerInternal)timer;
            @internal.Reuse(handle);
            @internal.SetTime(time);
            @internal.SetCount(count);
            @internal.SetDelay(delay);
            @internal.SetIsAutoKill(isAutoKill);
            @internal.SetIsForceAttach(isForceAttach);
            @internal.AutoKill += h => Remove(h);
            return timer;
        }

        public async UniTask Start(F64 time,
                                   uint count = 1,
                                   F64 delay = default,
                                   ILockstepTimer.DataDelegate onTimer = null,
                                   ILockstepTimer.DataDelegate onComplete = null,
                                   ILockstepTimer.DataDelegate onStart = null,
                                   ILockstepTimer.DataDelegate onKill = null,
                                   ILockstepTimer.DataDelegate onUpdate = null,
                                   ILockstepTimer.DataDelegate onTimerStart = null,
                                   LockstepTimerCancellationToken cancellationToken = null,
                                   bool isForceAttach = false) {
            var timer = AddTimer(time, count, delay, true, onTimer, onComplete, onStart, onKill, onUpdate, onTimerStart, isForceAttach);
            var isCancelled = false;
            var @internal = (ILockstepTimerInternal)timer;
            @internal.Cancelled += () => {
                isCancelled = true;
                Remove(timer.Handle);
            };
            var token = cancellationToken as ILockstepTimerCancellationTokenInternal;
            if (token != null) {
                Assert.IsFalse(token.IsDisposed);
                token.Cancel += () => {
                    @internal.Cancel();
                };
            }
            await KHBD.Context.WaitUntil(() => isCancelled || timer.IsKilled || timer.IsComplete);
            token?.Dispose();
        }

        void ILockstepTimerServiceInternal.Remove(LockstepTimerHandle handle) => DoRemove(handle, true);
        public void Remove(LockstepTimerHandle handle) => DoRemove(handle, false);
        private void DoRemove(LockstepTimerHandle handle, bool isForce) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                m_Dictionary.Remove(handle);
                (timer as ILockstepTimerInternal).Removing();
                if (timer.IsForceAttach || isForce) {
                    (LockstepService.Instance as ILockstepServiceInternal).DetachEntity(handle);
                } else {
                    LockstepService.Instance.DetachEntity(handle);
                }
            }
        }

        public void Pause(LockstepTimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                (timer as ILockstepTimerInternal).SetIsPause(true);
                return;
            }
            throw new InvalidOperationException();
        }

        public void Resume(LockstepTimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                (timer as ILockstepTimerInternal).SetIsPause(false);
                return;
            }
            throw new InvalidOperationException();
        }

        public bool IsPausing(LockstepTimerHandle handle) {
            if (m_Dictionary.TryGetValue(handle, out var timer)) {
                return timer.IsPausing;
            }
            throw new InvalidOperationException();
        }
    }
}

namespace Internal.KuiHuaBaoDian.Services.Lockstep {

    public interface ILockstepTimerServiceInternal {

        public interface IReusable {

            void Reuse(LockstepTimerHandle handle);

            void Recycle();
        }

        void Remove(LockstepTimerHandle handle);
    }
}
